﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Kernel
{
    public static class GraphGenerator
    {
        private static readonly Random Random = new Random((int)DateTime.Now.Ticks);

        public static Graph CreateRing(int nodeCount, int firtNumber = 0)
        {
            var result = new Graph();

            if (nodeCount < 2)
                throw new ArgumentOutOfRangeException(string.Format("Параметр nodeCount должен быть больше 2, но имеет значение {0}", nodeCount));
            var lastNode = new GraphNode(firtNumber.ToString());

            for (var i = 1; i < nodeCount; i++)
            {
                var currentNode = new GraphNode((i + firtNumber).ToString());
                currentNode.AddChild(lastNode);
                result.AddNode(lastNode);
                lastNode = currentNode;
                if(i == nodeCount - 1)
                    result.AddNode(lastNode);
            }

            result.GetNodeByName(firtNumber.ToString()).AddChild(result.GetNodeByName((nodeCount + firtNumber - 1).ToString()));
            //result.GetNodeByName((nodeCount + firtNumber - 1).ToString()).IsInitiator = true;
            //result.GetNodeByName("0").IsInitiator = true;

            return result;
        }

        private static List<GraphNode> AddChildrenToTree(int depth, int minChildCount, int maxChildCount, int curName = 0)
        {
            if (depth < 0)
                throw new ArgumentOutOfRangeException(string.Format("Параметр depth должен иметь значение больше нуля, а имеет значение {0}", depth));
            var root = new GraphNode(curName.ToString());
            var result = new List<GraphNode> { root };
            if (depth == 0)
            {
                //result[0].IsInitiator = true; 
                return result;
            }
            var n = Random.Next(minChildCount, maxChildCount);
            var newCurName = curName;
            for (var i = 0; i < n; i++)
            {
                var children = AddChildrenToTree(depth - 1, minChildCount, maxChildCount, ++newCurName);
                newCurName = children.Max(c => Convert.ToInt32(c.Name));
                var childrenRoot = children[0];
                childrenRoot.AddChild(root);
                root.AddChild(childrenRoot);
                result.AddRange(children);
            }
            return result;
        }

        public static Graph CreateTree(int depth, int minChildCount, int maxChildCount)
        {
            if (depth < 2) throw new ArgumentOutOfRangeException(string.Format("Параметр depth должен иметь значение больше единицы, а имеет значение {0}", depth));
            if (minChildCount < 1 || maxChildCount < minChildCount) throw 
                new ArgumentOutOfRangeException(string.Format(@"Некорректные параметры дерева?"));

            var result = new Graph();
            result.AddNodes(AddChildrenToTree(depth, minChildCount, maxChildCount));
            return result;
        }

        public static Graph CreateCompleteGraph(int count)
        {
            var result = new Graph();
            for (var i = 0; i < count; i++)
                result.AddNode(new GraphNode(i.ToString()));
            foreach (var n1 in result.Nodes)
                foreach (var n2 in result.Nodes.Where(t => t != n1))
                    n1.AddChild(n2);
            return result;
        }

        public static Graph CreateArbitraryBothGraph(int nodeCount, int relationCount)
        {
            if (relationCount < nodeCount - 1 || relationCount > nodeCount * (nodeCount - 1) / 2)
                throw new ArgumentOutOfRangeException();
            var result = CreateTree(nodeCount, 1, 1);
            relationCount -= (nodeCount - 1);
            while (relationCount-- > 0)
            {
                while (true)
                {
                    var nodeIndex1 = Random.Next(0, nodeCount);
                    var nodeIndex2 = Random.Next(0, nodeCount);
                    if (nodeIndex1 == nodeIndex2)
                        continue;
                    var node1 = result.Nodes[nodeIndex1];
                    var node2 = result.Nodes[nodeIndex2];
                    if (node1.Children.Any(n => n.Name == node2.Name))
                        continue;
                    node1.AddChild(node2);
                    node2.AddChild(node1);
                    break;
                }
            }

            return result;
        }
        public static Graph CreateArbitraryGraph(int nodeCount, int relationCount)
        {
            if (relationCount < nodeCount - 1 || relationCount > nodeCount * (nodeCount - 1))
                throw new ArgumentOutOfRangeException();
            var result = CreateRing(nodeCount);
            relationCount -= nodeCount;
            while (relationCount-- > 0)
            {
                while (true)
                {
                    var nodeIndex1 = Random.Next(0, nodeCount);
                    var nodeIndex2 = Random.Next(0, nodeCount);
                    if (nodeIndex1 == nodeIndex2)
                        continue;
                    var node1 = result.Nodes[nodeIndex1];
                    var node2 = result.Nodes[nodeIndex2];
                    if (node1.Children.Any(n => n.Name == node2.Name) || node2.Children.Any(n => n.Name == node1.Name))
                        continue;
                    node1.AddChild(node2);
                    break;
                    //node2.AddChild(node1);
                }
            }

            return result;
        }

        private static List<GraphNode> CreateHyperCubeListNode(int dim, int nodeName = 0)
        {
            if(dim < 0)
                throw new ArgumentOutOfRangeException(string.Format("Размерность гиперкуба должна быть больше неотрицательной, а имеет значение {0}", dim));
            if (dim == 0)
                return new List<GraphNode> {new GraphNode(nodeName.ToString())};
            var c1 = CreateHyperCubeListNode(dim - 1, nodeName);
            var c2 = CreateHyperCubeListNode(dim - 1, (int)Math.Pow(2, dim - 1) + nodeName);
            for (var i = 0; i < c1.Count; i++)
            {
                c1[i].Children.Add(c2[i]);
                c2[i].Children.Add(c1[i]);
            }
            return c1.Union(c2).ToList();
        }

        public static Graph CreateHyperCube(int dim)
        {
            var result = new Graph();
            result.AddNodes(CreateHyperCubeListNode(dim));
            return result;
        }

        public static Graph CreateTorus(int depth, int rank)
        {
            var result = new Graph();
            var rings = new List<List<GraphNode>>();
            for (var i = 0; i < depth; i++)
            {
                var ring = CreateRing(rank, (rank + 1)*i).Nodes;
                rings.Add(ring);
            }

            for (var i = 0; i < rank; i++)
                for (var j = 0; j < depth; j++)
                    rings[j][i].AddChild(rings[(j+1)%depth][i]);
            foreach (var ring in rings)
                result.AddNodes(ring);
            return result;
        }
    }
}
